#!/usr/bin/python
# YPG (Yep a Password Generator) Is a random passaword generator tool and more
#Copyright (C) 2011  Patrick Charron
#This program is free software; you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation; either version 2 of the License, or
#3 any later version.This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#GNU General Public License for more details.
#You should have received a copy of the GNU General Public License
#along with this program; if not, write to the Free Software
#Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  U

import crypt
import random
import sys
import getopt
import os
import getpass
import math
import pwd
import spwd


# Try to be PAM aware only on Linux.
try:
    import PAM
except ImportError:
    pam_aware = False
else:
    pam_aware = False


random = random.SystemRandom()



def capa():
        
    capas = ""
    
    if PAM:
        capas = capas + "PAM;"
    
    print "Info : " 
    print "OS = " + sys.platform
    print "Capabilities = " + capas



def help():
    print """Usage: ypg [OPTION]
Random Password Generator.

  -1                               use only number to create password
  -a                               use only letter to create password
  -c                               compute a compatible crypt password for /etc/shadow
  -C                               output capabilities
  -H=[0-6]                         select hash algorithm o=des, 1=md5, ..., 6=sha512. 6 is default
  -m                               modify user password.
  -n=lenght                        password lenght. 8 is default
  -p                               include ponctuation in password
  -P, --PASSWORD                   set password manualy
  -s, --salt                       set the salt lenght, default is 16
  -S, --strenght=<1 to 10>         easily set password strenght, value 1 to 10
  -t                               test user password, use with the -u option to specify a username
  -u=<username>                    specify a user
  -h,  --help                      display this help and exit
  -v,  --version                   output version information and exit

Examples:
  ypg -n 12 -c -H 1   Create a 12 lenght password, plus it generate a crypt password with hash md5.
  ypg -n 15 -a        Create a 15 lenght password only with letter."""     

def pam_conv(auth, query_list, userData):
    resp = []
    for i in range(len(query_list)):
        query, type = query_list[i]
        if type == PAM.PAM_PROMPT_ECHO_ON:
            val = raw_input(query)
            resp.append((val, 0))
        elif type == PAM.PAM_PROMPT_ECHO_OFF:
            val = getpass(query)
            resp.append((val, 0))
        elif type == PAM.PAM_PROMPT_ERROR_MSG or type == PAM.PAM_PROMPT_TEXT_INFO:
            print query
            resp.append(('', 0))
        else:
            return None
    return resp



def chpasswd_pam(password, username):
    auth = PAM.pam()
    auth.start("passwd")
    auth.set_item(PAM.PAM_USER, username)
    auth.set_item(PAM.PAM_CONV, pam_conv)
    auth.chauthtok()


def chpasswd(hash_string, username):
    #Just try to do a lock file and maybe use fcntl
    f = open("shadow.lock", "a")
    f.write(str(os.getpid()) + "\n")
    f.close()
    
    #Read the shadow database
    f = open("/etc/shadow" ,"r")
    db = f.read()
    f.close()
    
    #find the username
    new_db = ""
    found = False
    for line in db.split("\n"):
        if line.split(":")[0] == username:
            line = line.split(":")
            line[1] = hash_string
            new_line = ":".join(line)
            new_db = new_db + new_line + "\n"
            found = True
        else:
            new_db = new_db + line + "\n"

    if found:
        f = open("/etc/shadow", "r+")
        f.write(new_db[0:-1])
        f.close()
    else:
        print "ypg : " + username + " : username does not exist"
        sys.exit(1)    
    
    
def hash_gene(password, algo, salt_lenght):
    letter = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
    salt = ""
    for i in range(salt_lenght):
        salt = salt + random.choice(letter)
    if algo == 0:
        return crypt.crypt(password, salt)
    else:
        return crypt.crypt(password, "$" + str(algo) + "$" + salt)

def rand_passwd(mode, number):
    ponc = ["!", ".", "-", "_", "+", "-", "=", "$", "%", "?"]
    nb = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]
    letter = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
    
    if mode == 1:
        passwd = ""
        for i in range(number):
            passwd = passwd + random.choice(letter)
        return passwd
        
    if mode == 2:
        passwd = ""
        for i in range(number):
            passwd = passwd + random.choice(nb)
        return passwd
        
    if mode == 3:
        passwd = ""
        for i in range(number):
            passwd = passwd + random.choice(letter+nb)
        return passwd
    
    if mode == 4:
        not_find = True
        while not_find:
            passwd = ""
	    for i in range(number):
                passwd = passwd + random.choice(letter+nb+ponc)
            for i in passwd:
	        if i in ponc:
	            not_find = False
        return passwd

def main(argv):
    nb_of_carac = 8
    generate = True
    strenght = 0
    output_crypt = False
    inc_ponc = False
    only_letter = False
    only_number = False
    hash_algo = 6
    version = "0.3-alpha"
    test = False
    username = ""
    crypto = ""
    salt_lenght = 16
    password = ""
    modify = False
    
    try:
        opts, args = getopt.getopt(argv, "H:n:s:u:P:S:tpmvha1cC", ["HASH=", "PASSWORD=", "salt=", "user=", "help", "version" ])                                
    except getopt.GetoptError:           
        help()                         
        sys.exit(1)

    for opt, arg in opts: 
               
        if opt in ("-h", "--help"):      
            help()                     
            sys.exit(0)

        elif opt in ("-v", "--version"):
            print "Yep a Password Generator"
            print "ypg " + version + "\n"
            capa()
            print "\nCopyright (C) 2011 Free Software Foundation, Inc.\nLicense GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\nThis is free software: you are free to change and redistribute it.\nThere is NO WARRANTY, to the extent permitted by law.\n"
            sys.exit(0)

        elif opt in ("-H", "--HASH"):
	    try:
                hash_algo = int(arg)
            except ValueError:
	        print "ypg : " + arg + " : Invalid hash algorithm"
	        sys.exit(1)
	    else:
	        if hash_algo > 6:
		    print "ypg : " + arg + " : Invalid hash algorithm"
		    sys.exit(1)
		if hash_algo < 0:
		    print "ypg : " + arg + " : Invalid hash algorithm"
		    sys.exit(1)
		    
        elif opt in ("-s", "--salt"):
	    try:
                salt_lenght = int(arg)
            except ValueError:
	        print "ypg : " + arg + " : Invalid salt"
	        sys.exit(1)
	    else:
	        if salt_lenght > 32:
		    print "ypg : " + arg + " : Invalid salt"
		    sys.exit(1)
		if salt_lenght < 2:
		    print "ypg : " + arg + " : Invalid salt"
		    sys.exit(1)
        
        elif opt in ("-P", "--PASSWORD"):
	    if arg == "":
                print "ypg : : Password cannot be none"
                sys.exit(1)
            else:
	        generate = False
                password = arg
                
        elif opt == "-m":
	    modify = True

        elif opt == "-vv":
            help()

        elif opt == "-n":
	    try:
                nb_of_carac = int(arg)
            except ValueError:
	        print "ypg : "  + arg + " : Invalid number of caracter"
	        sys.exit(1)
	        
	elif opt == "-S":
	    try:
	        strenght = int(arg)
            except ValueError:
	        print "ypg : " + arg + " : Invalid password strenght value"
	        sys.exit(1)
	    else:
	        if strenght > 10:
		    print "ypg : " + arg + " : Invalid password strenght value"
		    sys.exit(1)
		elif strenght < 1:
		    print "ypg : " + arg + " : Invalid password strenght value"
		    sys.exit(1)
	        
        elif opt in ("-u", "--user"):
            if not os.geteuid() == 0:
                print "ypg must be run as root with the -u parameter"
                sys.exit(1)
	    else:
                username = arg

	elif opt == "-t":
            if not os.geteuid() == 0:
                print "ypg must be run as root with the -t parameter"
                sys.exit(1)
            else:
                test = True

        elif opt == "-p":
            if only_letter:
	        print "ypg : -p : Incompatible with option -l"
	        sys.exit(1)
	    elif only_number:
	        print "ypg : -p : Incompatible with option -1"
	        sys.exit(1)
	    else:
	        inc_ponc = True

        elif opt == "-a":
            if inc_ponc:
	        print "ypg : -a : Incompatible with option -p"
	        sys.exit(1)  
	    elif only_number:
	        print "ypg : -a : Incompatible with option -1"
	        sys.exit(1)
            else:
	        only_letter = True
	        
	elif opt == "-1":
            if inc_ponc:
	        print "ypg : -1 : Incompatible with option -p"
	        sys.exit(1)  
	    elif only_letter:
	        print "ypg : -1 : Incompatible with option -a"
	        sys.exit(1)
            else:
	        only_number = True

        elif opt == "-c":
	    output_crypt = True
	    
	elif opt == "-C":
	    capa()
	    sys.exit(0)


    if test:
        if username == "":
            print "ypg : : cannot test a null user"
            sys.exit(1)
        else:
            try:
                crypt_pass = spwd.getspnam(username)[1]
            except KeyError:
                print "ypg : " + username + " : User does not exist"
                sys.exit(1)
            password = getpass.getpass()
            if crypt.crypt(password, crypt_pass)  == crypt_pass:
                print "Authentification Successful"
                sys.exit(0)
            else:
                print "Authentification Fail"
        	sys.exit(1)

    if generate:

        if only_letter:
	    if strenght != 0:
	        entropy = (strenght * 80) / 10
	        nb_of_carac = int(math.ceil(entropy / (math.log(52) / math.log(2))))
            password = rand_passwd(1, nb_of_carac)
        elif only_number:
	    if strenght != 0:
	        entropy = (strenght * 80) / 10
	        nb_of_carac = int(math.ceil(entropy / (math.log(10) / math.log(2))))
            password = rand_passwd(2, nb_of_carac)
        elif inc_ponc:
	    if strenght != 0:
	        entropy = (strenght * 80) / 10
	        nb_of_carac = int(math.ceil(entropy / (math.log(71) / math.log(2))))
            password = rand_passwd(4, nb_of_carac)
        else:
	    if strenght != 0:
	        entropy = (strenght * 80) / 10
	        nb_of_carac = int(math.ceil(entropy / (math.log(62) / math.log(2))))
            password = rand_passwd(3, nb_of_carac)

    
    if output_crypt:
        crypto = hash_gene(password, hash_algo, salt_lenght)
        print password
        print crypto
    else:
        print password

    
    if modify:
        if username == "":
	    uid = os.geteuid()
	    username = pwd.getpwuid(uid)[0]
	  
        if crypto == "":
            crypto = hash_gene(password, hash_algo, salt_lenght)
        
        if pam_aware:
	    chpasswd_pam(password, username)
	else:
	    chpasswd(crypto, username)


if __name__ == "__main__":
    main(sys.argv[1:])








